/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cegg.graph;

import cegg.graph.connection.AndConnection;
import cegg.graph.vertex.EffectVertex;
import cegg.graph.vertex.Vertex;
import cegg.graph.vertex.IntermediateVertex;
import cegg.graph.vertex.ReasonVertex;
import cegg.graph.connection.Connection;
import cegg.graph.connection.EqualConnection;
import cegg.graph.connection.NotConnection;
import cegg.graph.connection.OrConnection;
import cegg.graph.dependency.Dependency;
import cegg.graph.dependency.NegativeDependency;
import cegg.graph.dependency.PositiveDependency;
import cegg.graph.restriction.AtLeastOneRestriction;
import cegg.graph.restriction.AtMostOneRestriction;
import cegg.graph.restriction.ExactOneRestriction;
import cegg.graph.restriction.Restriction;
import java.awt.Color;
import java.awt.Graphics;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.management.relation.RelationException;

/**
 * Class represents a graph as a collection of vertexes.
 *
 * @author kumper
 */
public final class Graph implements Serializable
{

    private Set<Vertex> vertexSet;
    private Set<Connection> connectionSet;
    private Set<Dependency> dependencySet;
    private Set<Restriction> restrictionSet;

    public Graph()
    {
        this.vertexSet = new HashSet<Vertex>();
        this.connectionSet = new HashSet<Connection>();
        this.dependencySet = new HashSet<Dependency>();
        this.restrictionSet = new HashSet<Restriction>();
    }

    public Set<Connection> getConnectionSet() {
        return connectionSet;
    }

    public Set<Dependency> getDependencySet() {
        return dependencySet;
    }

    public Set<Restriction> getRestrictionSet() {
        return restrictionSet;
    }

    public Graph createReason(int id, int positionX, int positionY)
    {
        if (this.getVertex(id) == null)
        {
            vertexSet.add(new ReasonVertex(id, positionX, positionY));
        }
        else
        {
            throw new IllegalArgumentException("There is already a vertex with id=" + id);
        }

        return this;
    }

    public Graph createEffect(int id, int positionX, int positionY)
    {
        if (this.getVertex(id) == null)
        {
            vertexSet.add(new EffectVertex(id, positionX, positionY));
        }
        else
        {
            throw new IllegalArgumentException("There is already a vertex with id=" + id);
        }

        return this;
    }

    public Graph createIntermediate(int id, int positionX, int positionY)
    {
        if (this.getVertex(id) == null)
        {
            vertexSet.add(new IntermediateVertex(id, positionX, positionY));
        }
        else
        {
            throw new IllegalArgumentException("There is already a vertex with id=" + id);
        }

        return this;
    }
    
    public boolean deleteVertex(int id)
    {
        Vertex v = this.getVertex(id);
        
        if (v != null)
        {
            vertexSet.remove(v);
            return true;
        }
        
        return false;
    }

    public Vertex getVertex(int id)
    {
        for (final Vertex v : vertexSet)
        {
            if (v.getId() == id)
            {
                return v;
            }
        }
        return null;
    }

    public List<Vertex> getReasonVertices()
    {
        List<Vertex> ret = new ArrayList<Vertex>();

        for (Vertex v : this.vertexSet)
        {
            if (v instanceof ReasonVertex)
            {
                ret.add(v);
            }
        }

        return ret;
    }

    public List<Vertex> getIntermediateVertices()
    {
        List<Vertex> ret = new ArrayList<Vertex>();

        for (Vertex v : this.vertexSet)
        {
            if (v instanceof IntermediateVertex)
            {
                ret.add(v);
            }
        }

        return ret;
    }

    public List<Vertex> getEffectVertices()
    {
        List<Vertex> ret = new ArrayList<Vertex>();

        for (Vertex v : this.vertexSet)
        {
            if (v instanceof EffectVertex)
            {
                ret.add(v);
            }
        }

        return ret;
    }

    //gets a list of vertices, which are connected with current vertex with
    //"AND" conection
    public Set<Vertex> getAndVerticesForVertex(int vertexId)
    {
        Set<Vertex> ret = new HashSet<Vertex>();

        for (final Connection connection : this.connectionSet)
        {
            if (connection instanceof AndConnection)
            {
                if (vertexId == connection.getEnd().getId())
                {
                    ret.add(connection.getStart());
                }
            }
        }

        return ret;
    }

    //gets a list of vertices, which are connected with current vertex with
    //"OR" conection
    public Set<Vertex> getOrVerticesForVertex(int vertexId)
    {
        Set<Vertex> ret = new HashSet<Vertex>();

        for (final Connection connection : this.connectionSet)
        {
            if (connection instanceof OrConnection)
            {
                if (vertexId == connection.getEnd().getId())
                {
                    ret.add(connection.getStart());
                }
            }
        }

        return ret;
    }

    public boolean isConnected(int idStart, int idEnd)
    {
        Vertex start = this.getVertex(idStart);
        Vertex end = this.getVertex(idEnd);

        if (start == null)
        {
            throw new IllegalArgumentException("Can't connect: Vertex with id:" + idStart + " doesn't exists!");
        }

        if (end == null)
        {
            throw new IllegalArgumentException("Can't connect: Vertex with id:" + idEnd + " doesn't exists!");
        }

        for (final Connection con : connectionSet)
        {
            if (con.exists(start, end))
            {
                return true;
            }
        }
        return false;
    }

    protected void checkViolations(Vertex start, Vertex end) throws RelationException
    {
        if (start == null)
        {
            throw new IllegalArgumentException("Can't connect: Vertex with id:" + start + " doesn't exists!");
        }

        if (end == null)
        {
            throw new IllegalArgumentException("Can't connect: Vertex with id:" + end + " doesn't exists!");
        }

        if (start == end)
        {
            throw new RelationException("Cannot connect vertex with itself: this is not supported");
        }

        if (this.isConnected(start.getId(), end.getId())) {
            throw new RelationException("Connection already exists!");
        }
    }

    public Graph connectEqual(int idStart, int idEnd) throws RelationException
    {
        Vertex start = this.getVertex(idStart);
        Vertex end = this.getVertex(idEnd);

        this.checkViolations(start, end);

        connectionSet.add(new EqualConnection(start, end));

        return this;
    }

    public Graph connectNot(int idStart, int idEnd) throws RelationException
    {
        Vertex start = this.getVertex(idStart);
        Vertex end = this.getVertex(idEnd);

        this.checkViolations(start, end);

        connectionSet.add(new NotConnection(start, end));

        return this;
    }

    public Graph connectOr(int idStart, int idEnd) throws RelationException
    {
        Vertex start = this.getVertex(idStart);
        Vertex end = this.getVertex(idEnd);

        this.checkViolations(start, end);

        connectionSet.add(new OrConnection(start, end));

        return this;
    }

    public Graph connectAnd(int idStart, int idEnd) throws RelationException
    {
        Vertex start = this.getVertex(idStart);
        Vertex end = this.getVertex(idEnd);

        this.checkViolations(start, end);

        connectionSet.add(new AndConnection(start, end));

        return this;
    }

    public Graph makePositiveDependency(int idStart, int idEnd) throws RelationException
    {
        Vertex start = this.getVertex(idStart);
        Vertex end = this.getVertex(idEnd);

        this.checkViolations(start, end);

        //TODO: handle error if idStart or idEnd is not an instance of ReasonVertex
        dependencySet.add(new PositiveDependency((ReasonVertex) start, (ReasonVertex)end));

        return this;
    }

    public Graph makeNegativeDependency(int idStart, int idEnd) throws RelationException
    {
        Vertex start = this.getVertex(idStart);
        Vertex end = this.getVertex(idEnd);

        this.checkViolations(start, end);

        //TODO: handle error if idStart or idEnd is not an instance of ReasonVertex
        dependencySet.add(new NegativeDependency((ReasonVertex) start, (ReasonVertex)end));

        return this;
    }

    public Graph makeAtLeastOneRestriction(int idStart, int idEnd) throws RelationException
    {
        Vertex start = this.getVertex(idStart);
        Vertex end = this.getVertex(idEnd);

        this.checkViolations(start, end);

        //TODO: handle error if idStart or idEnd is not an instance of ReasonVertex
        restrictionSet.add(new AtLeastOneRestriction((ReasonVertex) start, (ReasonVertex)end));

        return this;
    }

    public Graph makeAtMostOneRestriction(int idStart, int idEnd) throws RelationException
    {
        Vertex start = this.getVertex(idStart);
        Vertex end = this.getVertex(idEnd);

        this.checkViolations(start, end);

        //TODO: handle error if idStart or idEnd is not an instance of ReasonVertex
        restrictionSet.add(new AtMostOneRestriction((ReasonVertex) start, (ReasonVertex)end));

        return this;
    }

    public Graph makeExactOneRestriction(int idStart, int idEnd) throws RelationException
    {
        Vertex start = this.getVertex(idStart);
        Vertex end = this.getVertex(idEnd);

        this.checkViolations(start, end);

        //TODO: handle error if idStart or idEnd is not an instance of ReasonVertex
        restrictionSet.add(new ExactOneRestriction((ReasonVertex) start, (ReasonVertex)end));

        return this;
    }

    public int countReasons()
    {
        int ret = 0;

        for (final Vertex v : vertexSet)
        {
            if (v instanceof ReasonVertex)
            {
                ret++;
            }
        }

        return ret;
    }

    public int countEffects()
    {
        int ret = 0;

        for (final Vertex v : vertexSet)
        {
            if (v instanceof EffectVertex)
            {
                ret++;
            }
        }

        return ret;
    }

    public int countIntermediate()
    {
        int ret = 0;

        for (final Vertex v : vertexSet)
        {
            if (v instanceof IntermediateVertex)
            {
                ret++;
            }
        }

        return ret;
    }

    public void paint(Graphics g)
    {
        //connections
        for (final Connection c : connectionSet)
        {
            double x1 = c.getStart().getPositionX();
            double y1 = c.getStart().getPositionY();
            double x2 = c.getEnd().getPositionX();
            double y2 = c.getEnd().getPositionY();

            g.drawLine((int)x1, (int)y1, (int)x2, (int)y2);

            x1 += 0.00000001;
            y1 += 0.00000002;
            x2 += 0.00000003;
            y2 += 0.00000004;

            double sin = (Math.abs(-y2+y1))/(Math.sqrt(Math.pow(-y2+y1, 2)+Math.pow(x2-x1, 2)));
            double cos = (Math.abs(x2-x1))/(Math.sqrt(Math.pow(-y2+y1, 2)+Math.pow(x2-x1, 2)));

            int arrowheadX = (new Double(x2+15*cos*(x2>x1?-1:1))).intValue();
            int arrowheadY = (new Double(y2+15*sin*(y2>y1?-1:1))).intValue();
            int startAngle = (new Double((180*Math.asin(sin))/Math.PI)).intValue();
            startAngle += (x2>x1?((y2>y1)?0:-(startAngle%90)*2):((y2>y1)?90:180));

            g.fillArc(arrowheadX-15, arrowheadY-15, 30, 30, 160-startAngle, 40);

            if (c instanceof OrConnection)
            {
                g.drawArc((int)x2-30,
                          (int)y2-30,
                          60,
                          60,
                          90,
                          180);
                g.drawLine((int)x2, (int)y2+27, (int)x2-3, (int)y2+22);
                g.drawLine((int)x2, (int)y2+27, (int)x2+3, (int)y2+22);
            }

            if (c instanceof AndConnection)
            {
                g.drawArc((int)x2-32,
                          (int)y2-32,
                          64,
                          64,
                          90,
                          180);
                g.drawLine((int)x2, (int)y2+25, (int)x2-3, (int)y2+30);
                g.drawLine((int)x2, (int)y2+25, (int)x2+3, (int)y2+30);
            }

            if (c instanceof NotConnection)
            {
                int centerX = (int) ((x2>x1?x1:x2)+Math.abs((x2-x1)/2));
                int centerY = (int) ((y2>y1?y1:y2)+Math.abs((y2-y1)/2));

                g.drawArc(centerX-10,
                          centerY-5,
                          10,
                          10,
                          0,
                          180);
                g.drawArc(centerX,
                          centerY-5,
                          10,
                          10,
                          180,
                          180);
            }
        }

        //dependencies
        for (final Dependency dependency : dependencySet)
        {
            dependency.paint(g);
        }

        //restrictions
        for (final Restriction restriction : restrictionSet)
        {
            restriction.paint(g);
        }

        //vertices
        for (final Vertex v : vertexSet)
        {
            //outline circles
            g.setColor(Color.BLACK);
            g.fillOval(v.getPositionX()-15, v.getPositionY()-15, 30, 30);
            g.setColor(Color.DARK_GRAY);
            g.fillOval(v.getPositionX()-14, v.getPositionY()-14, 28, 28);
            g.setColor(Color.LIGHT_GRAY);
            g.fillOval(v.getPositionX()-13, v.getPositionY()-13, 26, 26);

            //vertexes
            if (v instanceof ReasonVertex)
            {
                g.setColor(Color.YELLOW);
            }
            if (v instanceof EffectVertex)
            {
                g.setColor(Color.GREEN);
            }
            if (v instanceof IntermediateVertex)
            {
                g.setColor(Color.GRAY);
            }
            g.fillOval(v.getPositionX()-12, v.getPositionY()-12, 24, 24);

            //vertexes' ids
            g.setColor(Color.BLACK);
            if (v.getId() < 10)
            {
                g.drawString(String.valueOf(v.getId()), v.getPositionX()-3, v.getPositionY()+5);
            }
            else
            {
                g.drawString(String.valueOf(v.getId()), v.getPositionX()-6, v.getPositionY()+5);
            }
        }
    }

}
